#include <unistd.h>
#include <libudev.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <time.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/wait.h>

#include "declares.h"
#include "log.h"
#include "test.h"
#include "work.h"
#include "tool.h"
#include "led.h"

#define ACT_CMP(devp, str) (strcmp(udev_device_get_action(devp), str))
#define NAME_CMP(devp, str) (strcmp(udev_device_get_sysname(devp), str))
#define GETATTR(devp, attr) char *attr = \
                                udev_device_get_sysattr_value(devp, "" #attr)

const char dev_name[16];
struct udev *udevp;
int started = 0;

const char *home_dir = NULL;
const char *mode = NULL;

work start_work = NULL;
cleanup clean = NULL;

int is_add(struct udev_device *devp)
{
    if (ACT_CMP(devp, "add") == 0)
    {
        DEBUG("%s: %s is added", __FUNCTION__,
              udev_device_get_sysname(devp));
        return 1;
    }
    return 0;
}

int is_remove(struct udev_deivce *devp)
{
    if (ACT_CMP(devp, "remove") == 0)
    {
        DEBUG("%s: %s is removed", __FUNCTION__,
              udev_device_get_sysname(devp));
        return 1;
    }
    return 0;
}

int has_id_attr(struct udev_device *devp)
{
    GETATTR(devp, idProduct);
    GETATTR(devp, idVendor);
    if (idProduct && idVendor)
        return 1;
    return 0;
}

int checkid(struct udev_device *devp)
{
    GETATTR(devp, idProduct);
    GETATTR(devp, idVendor);
    if ((strcmp(idProduct, "4e3d")) == 0 && (strcmp(idVendor, "1286")) == 0)
        return 1;
    return 0;
}

struct udev_device *get_parent(struct udev_device *devp)
{
    struct udev_device *parentp =
        udev_device_get_parent_with_subsystem_devtype(devp, "usb", NULL);
    if (!parentp)
    {
        return NULL;
    }
    while (has_id_attr(parentp) == 0)
    {
        parentp = udev_device_get_parent(parentp);
    }
    return parentp;
}

void do_add(struct udev_device *devp)
{
    if (started)
    {
        return;
    }

    struct udev_device *parentp = get_parent(devp);
    if (!parentp)
        return;
    if (checkid(parentp))
    {
        DEBUG("%s: id matched", __FUNCTION__);
        strcpy(dev_name, udev_device_get_sysname(devp));
        run_task();
        started = 1;
    }
}

void do_remove(struct udev_device *devp)
{
    if (!started)
        return;
    if (NAME_CMP(devp, dev_name) == 0)
    {
        task_cancel_req = 1;
        task_join();
        started = 0;
        task_cancel_req = 0;
    }
}

void parse_dev(struct udev_device *devp)
{
    if (is_add(devp))
    {
        do_add(devp);
    }

    if (is_remove(devp))
    {
        do_remove(devp);
    }
}

void sig_handler(int signo)
{
    DEBUG("%s %s got SIGINT signal", __FILE__, __FUNCTION__);
    if (started)
    {
        task_cancel_req = 1;
        task_join();
        started = 0;
    }
    led_cancel_req = 1;
    void *status;
    pthread_kill(led_th, SIGUSR1);
    pthread_join(led_th, &status);
    udev_unref(udevp);
    signal(SIGTERM, SIG_DFL);
    raise(SIGTERM);
}

void pass(int signo)
{
    DEBUG("pass");
    return;
}

int main(int argc, char **argv)
{
    mode = argv[1];
    home_dir = argv[2];
    DEBUG("diagd %s %s", mode, home_dir);

    if (strcmp(mode, "test") == 0)
    {
        start_work = start_test;
        clean = close_test;
    }
    else if(strcmp(mode, "log") == 0)
    {
        start_work = start_diag;
        clean = close_diag;
    }
    else
    {
        DEBUG("mode must be log or test");
        return 0;
    }
    
    struct sigaction act;
    struct udev_decice *devp;
    struct udev_monitor *monp;
    int fd;

    act.sa_handler = sig_handler;
    act.sa_flags = 0;
    sigaction(SIGINT, &act, NULL);

    act.sa_handler = pass;
    act.sa_flags = 0;
    sigaction(SIGUSR1, &act, NULL);

    pthread_create(&led_th, NULL, blingbling, NULL);
    char *_dev_name = NULL;

    if ((_dev_name = dev_name_f(E_DIAG)) != NULL)
    {
        strcpy(dev_name, _dev_name);
        run_task();
        started = 1;
    }

    udevp = udev_new();
    if (!udevp)
    {
        DEBUG("can't create udev");
        return 1;
    }

    monp = udev_monitor_new_from_netlink(udevp, "udev");
    udev_monitor_filter_add_match_subsystem_devtype(monp, "tty", NULL);
    udev_monitor_enable_receiving(monp);
    fd = udev_monitor_get_fd(monp);

    while (1)
    {
        fd_set fds;
        struct timeval tv;
        int ret;

        FD_ZERO(&fds);
        FD_SET(fd, &fds);
        tv.tv_sec = 0;
        tv.tv_usec = 0;

        ret = select(fd + 1, &fds, NULL, NULL, NULL);
        if (ret > 0 && FD_ISSET(fd, &fds))
        {
            while ((devp = udev_monitor_receive_device(monp)) != NULL)
            {
                parse_dev(devp);
                udev_device_unref(devp);
            }
        }
    }
    return 0;
}
